package top.jle.spring.basic.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import top.jle.jpa.basic.exception.BasicCustomException;
import top.jle.jpa.basic.exception.BasicExceptionCode;
import top.jle.spring.basic.annotation.OperationLock;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁切面
 * @author  jim.lee
 * @date 2022/5/11 10:08
 */
@Component
@Aspect
@Order(1)
public class OperationLockAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(OperationLockAspect.class);
    //加锁等待时间
    private static final Integer LOCK_WAIT_TIME = 5;
    private final RedissonClient redissonClient;


    public OperationLockAspect(@Autowired(required = false) RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }
    @Around("@annotation(operationLock)")
    public Object around(ProceedingJoinPoint point, OperationLock operationLock) throws Throwable {
        Object obj;
        String lockValue = parseExpression(point,operationLock);
        String keyPrefix = operationLock.lockKeyPrefix();

        String key = keyPrefix.endsWith(":")?(keyPrefix + lockValue):(keyPrefix+":"+lockValue);
        RLock lock = redissonClient.getLock(key);
        try{
            boolean lockResult = lock.tryLock(LOCK_WAIT_TIME,operationLock.releaseTime(), TimeUnit.SECONDS);
            LOGGER.info("acquire the lock:{}",key);
            if(lockResult){
                obj = point.proceed();
            }else{
                throw new BasicCustomException(BasicExceptionCode.REPEAT_REQUEST);
            }
            return obj;
        }finally {
            lock.unlock();
        }
    }
    /**
     * 解析锁表达式
     * @author jim.lee
     * @date: 2022/5/11 10:14
     */
    private String parseExpression(ProceedingJoinPoint point, OperationLock operationLock) throws NoSuchMethodException {
        String expressionString = operationLock.expression();
        Method method = getTargetMethod(point);
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = new MethodBasedEvaluationContext(new Object(),method,point.getArgs(),new DefaultParameterNameDiscoverer());
        Expression expression = parser.parseExpression(expressionString);
        return expression.getValue(context,String.class);
    }
    private Method getTargetMethod(ProceedingJoinPoint point) throws NoSuchMethodException {
       MethodSignature signature = (MethodSignature) point.getSignature();
       Method agentMethod = signature.getMethod();
       return point.getTarget().getClass().getMethod(agentMethod.getName(),agentMethod.getParameterTypes());
    }
}
